/**
 * 正则：匹配标签名
 * 开头不能包含特殊字符和数字
 * 第二个字符开始 可以是任意字符了 / \ 空白符 . 都可以
 *  div _div _ab88 a_9.//a
 *
 */
const ncname = "[a-zA-Z_][\\-\\.0-9a-zA-Z]*";

/**正则
 * 捕获 标签名
 * 注意 ?: 只匹配不捕获
 * 这里的匹配标签名 后面还有:的这种 是带命名空间的标签 比如 a:b
 */
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
/**正则
 * 匹配到的分组是一个 标签名 <div
 */
// ^<((?:[a-zA-Z_][\\-\\.0-9a-zA-Z]*\\:)?[a-zA-Z_][\\-\\.0-9a-zA-Z]*)
const startTagOpen = new RegExp(`^<${qnameCapture}`);
/**正则
 * 匹配标签名结束 <\/div> 因为 /具有特殊含义
 */
// ^<\\/((?:[a-zA-Z_][\\-\\.0-9a-zA-Z]*\\:)?[a-zA-Z_][\\-\\.0-9a-zA-Z]*)[^>]*>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);

/**正则
 * 匹配属性  a="abc" a='abc' a=abc a
 * 分组一的值就是键key 分组3/4/5匹配到的是value
 */
const attribute =
  /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;

/**正则
 * 匹配标签结束 >
 * 匹配以零个或多个空白字符开头的字符串，然后是零个或一个斜杠，最后是一个大于号
 * 标签可能自闭合 匹配 "  />"  "/>" "<div>"
 */
const startTagClose = /^\s*(\/?)>/;

/**
 * 匹配 双花括号语法 {{}} 匹配到的是就是双花括号的 变量
 */
export const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

// 具体的单个解析方法
/**
 * 解析 模板
 * @param {string} html 模板字符串
 * vue2采用正则编译解析 vue3不是采用正则了
 */
// 解析HTML 遍历HTML字符串 //<div id="app">hello: {{name}}<h></h></div>
function parseHTML(html) {
  //递归一直把所有字符串标签，包括嵌套子标签的情况等进行处理--->ast语法树
  // 创建一个ast对象 ==>语法树=== {}(就是个对象)   //<div id="app">hello: {{name}}</div> ===> 变成ast语法树 对象的 形式
  function createAstElement(tag, attrs) {
    return {
      tag, // div
      attrs, //属性
      children: [], //子节点
      type: 1, //在原生dom中 这些类型通过节点的`nodeType`属性 元素节点类型有 元素节点（1）   属性节点（`2`）文本节点（`3`）注释节点（`8`）`<!-- This is a comment -->`
      parent: null,
    };
  }
  let root; //根元素---作用域问题--在parseHTML内 每次调用parseHTML root会被初始化 重新判断
  let createParent; //当前元素的父亲
  //通过数据结构 栈 来找到当前元素的父亲是谁，比如上方的['div','h'] 然后把h删掉，['div']数组长度0就是 当前元素的父亲
  let stack = []; // ['div','h']

  function start(tag, attrs) {
    //字符串代码 开始标签处理
    console.log(tag, attrs, "start-嵌套的代码需要遍历");
    let element = createAstElement(tag, attrs);

    if (!root) {
      //根标签无，那么这个开始标签div就是根元素
      root = element;
    }
    createParent = element;
    stack.push(element);
  }

  function charts(text) {
    // 字符串代码 文本处理
    // console.log(text, 'text-嵌套代码-遍历')
    //文本里面可能有人输入 空格 所以 去掉空格
    text = text.replace(/\s/g, "");
    if (text) {
      createParent.children.push({
        type: 3,
        text,
      });
    }
  }

  function end(tag) {
    // 字符串代码 结束标签处理
    // console.log(tag, 'end-嵌套的代码需要遍历')
    let element = stack.pop(); //获取['div','h']的最后的元素====儿子
    createParent = stack[stack.length - 1];
    if (createParent) {
      //如果有父亲
      element.parent = createParent.tag;
      createParent.children.push(element); //完成拼接ast语法树 对象{}===> 父亲 下有个chilidren属性 里面有每个儿子
    }
  }
  // root = null; // 每次new Vue()一个实例 每次调用compileToFunction函数 每次传入一个新的模板字符串，root需要初始化
  //<div id="app">hello: {{name}}</div> ===> 变成ast语法树 对象的 形式
  while (html) {
    // debugger
    // while循环的特性 html为空结束
    // 判断标签 <>
    let textEnd = html.indexOf("<"); //0
    if (textEnd == 0) {
      //开始标签的<div 的<  或者是</div>的<
      // 标签
      //(1) 开始标签有<
      const startTagMatch = parseStartTag(); //开始标签的内容
      // console.log(startTagMatch,'startTagMatch---parseHTML')
      if (startTagMatch) {
        start(startTagMatch.tagName, startTagMatch.attrs);
        continue; //continue的作用: 跳出对< 标签的解析操作，去书写另一个逻辑，判断文本的逻辑
      }
      // 结束标签中也有<

      let endTagMatch = html.match(endTag);

      if (endTagMatch) {
        advance(endTagMatch[0].length); //继续删除该<
        end(endTagMatch[1]); //正则匹配到的内容传递过去
      }
    }
    // 判断文本 //文本 hell: {{message}}< 中的<位置是15，然后获取文本内容
    if (textEnd > 0) {
      let text = html.slice(0, textEnd);
      if (text) {
        charts(text);
        advance(text.length); //继续删除html
      }
    }

    function parseStartTag() {
      //解析开始标签 然后拼对象ast
      const startMatchValue = html.match(startTagOpen); //1. 返回结果 2. false
      console.log(startMatchValue, "startMatchValue112");
      let match = {};
      if (startMatchValue) {
        // 创建一个语法树
        match = {
          tagName: startMatchValue[1],
          attrs: [],
        };
        // while循环继续往后遍历新的html字符串,删除当前已经转成ast的字符串内容
        advance(startMatchValue[0].length); //删除开始标签
      }

      //属性操作
      // 注意： 多个属性 需要遍历
      // 注意  >
      let attr;
      let end;
      while (
        !(end = html.match(startTagClose)) &&
        (attr = html.match(attribute))
      ) {
        // console.log(attr, 'attr-属性');
        // 继续追加 到ast
        match.attrs.push({
          name: attr[1],
          value: attr[3] || attr[4] || attr[5],
        });
        //继续删除已经添加到ast中的字符串
        advance(attr[0].length);
      }
      // 然后去掉删除>标签  end表示startTagClose结束标签 上面先判断不是> 来获取<div id='app'></div> id='app'属性内容
      // console.log(end,"endend")
      if (end) {
        advance(end[0].length); //删除>标签
        //  console.log(match,'matchmatch112')
        return match; //返回语法树
      }
    }

    function advance(n) {
      html = html.substring(n);
      // console.log(html, "new-html-new-html");
    }
    // break; //让while循环一值循环，加break只循环一次，进行调试
  }
  console.log(root, html, "root1");
  return root;
}

export { parseHTML };
